(ns clj.silprob
  (:use [clj.sildiceprob :only (probabilityMap)])
  (:use [clojure.contrib.math :only (floor round)])
  (:import (container ProbabilityParameters))
  )

(def maximalDamage 5)

(defn- multKeyAndValue [k possibilityMap]
  (* k (get possibilityMap k 0))
  )

(defn expectedValue [possibilityMap]
  {:pre [(map? possibilityMap)]}
  "(expectedValue possibilityMap) returns the expected value based on the given possibilities"
  (let [
        minimum (reduce min(keys possibilityMap))
        maximum (reduce max(keys possibilityMap))
        ]
    (round (reduce + (map 
                multKeyAndValue 
                (range minimum (+ 1 maximum))
                (repeat possibilityMap)))
    ))  
  )

(defn minimumMoS [dm armor boxes]
  "(minimumMoS dm armor boxes) returns the minimal MoS which for the given DM and armor to achieve the specified damage."
  (cond 
    (= boxes 0)0
    :else(floor (/ (* armor boxes) dm))
  ))
  
(defn maximumMoS [dm armor boxes]
  "(maximumMos dm armor boxes) returns the maximal MoS which for the given DM and armor to achieve the specified damage."
  (- (floor (/ (* armor (+ boxes 1)) dm)) 1))

(defn- sumProbability [dm armor boxes mosProbability]
  (let [minimum (if (= boxes 0) 
                  (reduce min (keys mosProbability))
                  (minimumMoS dm armor boxes))
        maximum (if (= boxes maximalDamage)
                  (reduce max (keys mosProbability))
                  (maximumMoS dm armor boxes)
                  )
        ]
    (reduce + (map get (repeat mosProbability) (range minimum (+ 1 maximum)) (repeat 0))))
  )

(defn getDamageProbability [dm armor mosProbability]
  {:pre [(map? mosProbability)]}
  "(getDamageProbability dm armor mosProbability) returns a map which contains the probability to cause damage."
  (zipmap 
    (range 0 (+ 1 maximalDamage)) 
    (map sumProbability 
      (repeat dm) 
      (repeat armor) 
      (range 0 (+ 1 maximalDamage)) 
      (repeat mosProbability)))
  )

(defn getMoSProbability [mos attackProb defenseProb]
  {:pre [(map? attackProb) (map? defenseProb)]}
  "(getMosProbability mos attackProb defenseProb) returns the probability for the attacker to achieve the given MoS. 'dif' is the difference between the modifiers for the attacker and defender."
  (let [maximalAttackerRoll (reduce max (keys attackProb))
        parameters {:attackP attackProb,
                    :defenseP defenseProb,  
                    :m mos}]
    
    (loop [probSum 0, roll maximalAttackerRoll, param parameters]
      (let [attackRollProb  (get (param :attackP) roll 0)
            defenseRollProb (get (param :defenseP) (- roll (param :m)) 0)
            probSum (+ probSum (* attackRollProb defenseRollProb))]
        
        (if (> roll -1) (recur probSum (dec roll) param) probSum )))))

(defn- getExtraMoS 
  "(getExtraMos ap weakFacing) calculates the value by which the MoS is increased if the used weapon has AP and/or the defendant has Weak facing"
  [ap weakFacing]
  (cond
    (and ap weakFacing) 3
    (or ap weakFacing) 2
    :else 0))

(defn- increaseMoS
  "(increaseMoS n) returns a function which adds n to the MoS, if the MoS is 0 o higher"
  [n]
  (let [x n]
    (fn[y] (if (< y 0) y (+ y x))) 
  ))

(defn- applyModifierToProbabilityMap 
  "(applyModifierToProbabilityMap probMap modifier) applies the modifier to the specified map and returns a map which contains the probabilities for the possible values."
  [probMap modifier]
  {:pre [(map? probMap)]}
  (let [valueMap (into {}
                       (for [x (seq probMap)]
                         (if (= (key x) 0) 
                           [(key x) (val x)]
                           [(+ modifier (key x)) (val x)] )))
        lessOrEqualZero (filter #(< (key %) 1) valueMap) 
        sum (reduce + (vals lessOrEqualZero))]
    
    (assoc (reduce dissoc valueMap (keys lessOrEqualZero)) 0 sum)))

(defn getMoSMap 
  "(getMoSMap ^ProbabilityParameters parameters) expects that the passed object is from the type ProbabilityContainers and returns a map with the probabilities for all possible MoS based on the infomration in the passed object."
  ([^ProbabilityParameters parameters]
    (let [
          attackMod (if (. parameters isAttackThreshold) 0 
                      (. parameters getAttackModificator))
          defenseMod (if (. parameters isDefenceThreshold) 0 
                     (. parameters getDefenseModificator))
          attackerProb (if (. parameters isAttackThreshold)
                         {(. parameters getNumberOfAttackDices) 1}
                         (applyModifierToProbabilityMap (probabilityMap 
                                                          (. parameters getNumberOfAttackDices) 
                                                          (. parameters getDiceSides))
                                                        attackMod))
          defenseProb (if (. parameters isDefenceThreshold)
                        {(. parameters getNumberOfDefenseDices) 1}
                        (applyModifierToProbabilityMap (probabilityMap 
                                                         (. parameters getNumberOfDefenseDices) 
                                                         (. parameters getDiceSides))
                                                       defenseMod))
        
        maxMoS (reduce max (keys attackerProb))
        maxMoF (* -1 (reduce max (keys defenseProb)))
        extraMoS (increaseMoS (getExtraMoS (. parameters getAP) (. parameters getWeakFacing)))
        ]
        
    (zipmap 
          (map extraMoS (range maxMoF (+ 1 maxMoS))) 
          (map getMoSProbability 
               (range maxMoF (+ 1 maxMoS)) 
               (repeat attackerProb) 
               (repeat defenseProb))
          )
    ))
  )